package cn.zyjblogs.server.user.handler;

import org.jetbrains.annotations.NotNull;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidRequestException;
import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.AuthorizationRequest;
import org.springframework.security.oauth2.provider.ClientDetails;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;

import java.net.URI;
import java.util.*;

/**
 * @author zhuyijun
 */
public class OauthRquestHander {

    private OauthRquestHander() {

    }
    /**
     * 处理
     *
     * @param authorizationParameters
     * @param clientDetails
     * @return
     */
    public static AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters, ClientDetails clientDetails) {
        return getAuthorizationRequest(authorizationParameters, clientDetails);
    }

    @NotNull
    public static AuthorizationRequest getAuthorizationRequest(Map<String, String> authorizationParameters, ClientDetails clientDetails) {
        String clientId = authorizationParameters.get(OAuth2Utils.CLIENT_ID);
        String state = authorizationParameters.get(OAuth2Utils.STATE);
        String redirectUri = authorizationParameters.get(OAuth2Utils.REDIRECT_URI);
        Set<String> responseTypes = OAuth2Utils.parseParameterList(authorizationParameters
                .get(OAuth2Utils.RESPONSE_TYPE));
        Set<String> scopes = OAuth2Utils.parseParameterList(authorizationParameters.get(OAuth2Utils.SCOPE));
        if (CollectionUtils.isEmpty(scopes)) {
            scopes = clientDetails.getScope();
        }
        AuthorizationRequest request = new AuthorizationRequest(authorizationParameters,
                Collections.emptyMap(), clientId, scopes, null, null, false, state, redirectUri,
                responseTypes);
        request.setResourceIdsAndAuthoritiesFromClientDetails(clientDetails);
        return request;
    }

    public static String appendAccessToken(AuthorizationRequest authorizationRequest, OAuth2AccessToken accessToken) {

        Map<String, Object> vars = new LinkedHashMap<>();
        Map<String, String> keys = new HashMap<>();

        if (accessToken == null) {
            throw new InvalidRequestException("An implicit grant could not be made");
        }

        vars.put("access_token", accessToken.getValue());
        vars.put("refresh_token", accessToken.getRefreshToken());
        vars.put("token_type", accessToken.getTokenType());
        vars.put("expiration", accessToken.getExpiration());
        String state = authorizationRequest.getState();

        if (state != null) {
            vars.put("state", state);
        }
        Date expiration = accessToken.getExpiration();
        if (expiration != null) {
            long expires_in = (expiration.getTime() - System.currentTimeMillis()) / 1000;
            vars.put("expires_in", expires_in);
        }
        String originalScope = authorizationRequest.getRequestParameters().get(OAuth2Utils.SCOPE);
        if (originalScope == null || !OAuth2Utils.parseParameterList(originalScope).equals(accessToken.getScope())) {
            vars.put("scope", OAuth2Utils.formatParameterList(accessToken.getScope()));
        }
        Map<String, Object> additionalInformation = accessToken.getAdditionalInformation();
        for (String key : additionalInformation.keySet()) {
            Object value = additionalInformation.get(key);
            if (value != null) {
                keys.put("extra_" + key, key);
                vars.put("extra_" + key, value);
            }
        }
        // Do not include the refresh token (even if there is one)
        return append(authorizationRequest.getRedirectUri(), vars, keys, true);
    }

    private static String append(String base, Map<String, ?> query, Map<String, String> keys, boolean fragment) {

        UriComponentsBuilder template = UriComponentsBuilder.newInstance();
        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base);
        URI redirectUri;
        try {
            // assume it's encoded to start with (if it came in over the wire)
            redirectUri = builder.build(true).toUri();
        } catch (Exception e) {
            // ... but allow client registrations to contain hard-coded non-encoded values
            redirectUri = builder.build().toUri();
            builder = UriComponentsBuilder.fromUri(redirectUri);
        }
        template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost())
                .userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());

        if (fragment) {
            StringBuilder values = new StringBuilder();
            if (redirectUri.getFragment() != null) {
                String append = redirectUri.getFragment();
                values.append(append);
            }
            for (String key : query.keySet()) {
                if (values.length() > 0) {
                    values.append("&");
                }
                String name = key;
                if (keys != null && keys.containsKey(key)) {
                    name = keys.get(key);
                }
                values.append(name + "={" + key + "}");
            }
            if (values.length() > 0) {
                template.fragment(values.toString());
            }
            UriComponents encoded = template.build().expand(query).encode();
            builder.fragment(encoded.getFragment());
        } else {
            for (String key : query.keySet()) {
                String name = key;
                if (keys != null && keys.containsKey(key)) {
                    name = keys.get(key);
                }
                template.queryParam(name, "{" + key + "}");
            }
            template.fragment(redirectUri.getFragment());
            UriComponents encoded = template.build().expand(query).encode();
            builder.query(encoded.getQuery());
        }

        return builder.build().toUriString();
    }

    public static String append(String base, Map<String, ?> query, boolean fragment) {
        return append(base, query, (Map) null, fragment);
    }

    public static String getSuccessfulRedirect(AuthorizationRequest authorizationRequest, String authorizationCode) {
        if (authorizationCode == null) {
            throw new IllegalStateException("No authorization code found in the current request scope.");
        } else {
            Map<String, String> query = new LinkedHashMap<>();
            query.put("code", authorizationCode);
            String state = authorizationRequest.getState();
            if (state != null) {
                query.put("state", state);
            }

            return append(authorizationRequest.getRedirectUri(), query, false);
        }
    }

    public static String getUnsuccessfulRedirect(AuthorizationRequest authorizationRequest, OAuth2Exception failure, boolean fragment) {
        if (authorizationRequest != null && authorizationRequest.getRedirectUri() != null) {
            Map<String, String> query = new LinkedHashMap<>();
            query.put("error", failure.getOAuth2ErrorCode());
            query.put("error_description", failure.getMessage());
            if (authorizationRequest.getState() != null) {
                query.put("state", authorizationRequest.getState());
            }

            getFailFedirect(failure, query);

            return append(authorizationRequest.getRedirectUri(), query, fragment);
        } else {
            throw new UnapprovedClientAuthenticationException("Authorization failure, and no redirect URI.", failure);
        }
    }

    public static String getUnsuccessfulRedirect(String redirectUri, OAuth2Exception failure, boolean fragment) {
        if (StringUtils.hasLength(redirectUri)) {
            Map<String, String> query = new LinkedHashMap<>();
            query.put("error", failure.getOAuth2ErrorCode());
            query.put("error_description", failure.getMessage());
            getFailFedirect(failure, query);
            return append(redirectUri, query, fragment);
        } else {
            throw new UnapprovedClientAuthenticationException("Authorization failure, and no redirect URI.", failure);
        }
    }

    public static void getFailFedirect(OAuth2Exception failure, Map<String, String> query) {
        if (failure.getAdditionalInformation() != null) {
            Iterator<Map.Entry<String, String>> var5 = failure.getAdditionalInformation().entrySet().iterator();
            while (var5.hasNext()) {
                Map.Entry<String, String> additionalInfo = var5.next();
                query.put(additionalInfo.getKey(), additionalInfo.getValue());
            }
        }
    }
}
